#pragma once

#include "DirectXDependencies.h"
#include "d3dtypes.h"
#include "types.h"
#include <tuple>
#include "geom.h"
#include <memory>
#include <cassert>
using namespace std;
namespace d3d
{


	template<typename TCOM>
	struct ComObject
	{
		ComObject(TCOM* obj) : m_obj(obj) { }
		~ComObject() { m_obj->Release(); m_obj = nullptr; }
		TCOM* m_obj;
	};

	struct DXDevice;
	struct DXContext;

	struct VertexBuffer
	{
		Buffer* backing;
		uint stride;
	};
	struct IndexBuffer
	{
		Buffer* backing;
		uint indexes_count;
	};

	struct Shader
	{
		VS* vs;
		PS* ps;
		InputLayout* input_layout;
	};


	void resize(SwapChain* swap_chain, Device* device, Size desired_size, RenderTargetView** rtv);
	void resize(Device* device, Size desired_size, Texture2D** tex2d);

	void clear(Context* context, RenderTargetView* view, float4 value);
	void clear(Context* context, DepthStencilView* view, float value);


	InputLayout* create_inputlayout(Device* device, Blob* vs, VertexType vertex_type);
	Blob* create_shaderblob(FilePath path, const char* func_name, const char* type);
	VS* create_vs(Device* device, FilePath path);
	PS* create_ps(Device* device, FilePath path);

	VS* create_vs(Device* device, Blob* blob);
	PS* create_ps(Device* device, Blob* blob);

	Shader create_shader_and_input_layout(Device* device, FilePath path, VertexType vertex_type, const char* vs_func_name);

	VertexBuffer create_vb(Device* device, shared_ptr<Geometry> data);
	IndexBuffer create_ib(Device* device, shared_ptr<Geometry> data);

	Buffer* create_cb(Device* device, uint size);
	template<typename T>
	Buffer* create_cb(Device* device)
	{
		return create_cb(device, sizeof(T));
	}
	SamplerState* create_sampler_state(Device* device, D3D11_FILTER filter);

	void set_vs_cb(Context* context, Buffer* const* vs_cb, uint vs_cb_count);
	void set_ps_cb(Context* context, Buffer* const* ps_cb, uint ps_cb_count);
		
	void draw(Context* context, VertexBuffer& vertex_buffer, IndexBuffer& index_buffer,
		Shader shader,
		Buffer* const* vs_cb, uint vs_cb_count, Buffer* const* ps_cb, uint ps_cb_count);

	void draw(Context* context, VertexBuffer& vertex_buffer, IndexBuffer& index_buffer,
		VS* vs, PS* ps, InputLayout* input_layout,
		Buffer* const* vs_cb, uint vs_cb_count, Buffer* const* ps_cb, uint ps_cb_count);

	void set_buffers(Context* context, uint vb_stride, Buffer* vb, Buffer* ib, D3D11_PRIMITIVE_TOPOLOGY topology);

	Texture2D* create_depth_texture(Device* device, uint w, uint h, uint binding = D3D11_BIND_DEPTH_STENCIL);
	Texture2D* create_texture2d(Device* device, uint w, uint h, DXGI_FORMAT format, uint binding);

	DepthStencilView* create_dsv(Device* device, Texture2D* tex2d);
	RenderTargetView* create_rtv(Device* device, Texture2D* tex2d);
	RenderTargetView* create_rtv(Device* device, SwapChain* swapchain);
	ShaderResourceView* create_srv(Device* device, Texture2D* tex2d);
	ShaderResourceView* create_srv(Device* device, Texture2D* tex2d, DXGI_FORMAT format, D3D11_SRV_DIMENSION dimensionMS);

	Buffer* create_buffer(Device* device, const void* data_ptr, uint size, uint binding, D3D11_USAGE usage,
		D3D11_CPU_ACCESS_FLAG cpu_flag);
	Buffer* create_buffer(Device* device,  uint size, uint binding, D3D11_USAGE usage,
		D3D11_CPU_ACCESS_FLAG cpu_flag);

	ShaderResourceView* load_texture(Device* device, FilePath path, DXGI_FORMAT desired_format);
	
	template<typename T>
	T* lock_buffer(Context* context, Buffer* buffer)
	{
		D3D11_MAPPED_SUBRESOURCE ms;
		context->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
		return (T*)ms.pData;
	}
	void unlock_buffer(Context* context, Buffer* buffer);

	void set_viewport(Context* context, uint width, uint height);
	ViewPort create_viewport(uint width, uint height);


	template<typename T>
	void set_name(T* obj, const char* name)
	{
		obj->SetPrivateData( WKPDID_D3DDebugObjectName, (uint)strlen(name), name );
	}

	template<typename T>
	void destroy( T* obj )
	{
		T->Release();
	}


	struct DXDevice
	{
		DXDevice(unique_ptr<ID3D11Device> device);
		~DXDevice();

		Shader create_shader_and_input_layout(FilePath path, VertexType vertex_type, const char* vs_func_name)
		{
			return d3d::create_shader_and_input_layout(m_device.get(), path, vertex_type, vs_func_name);
		}

		VertexBuffer create_vb(shared_ptr<Geometry> data)
		{
			return d3d::create_vb(m_device.get(), data);
		}
		IndexBuffer create_ib(shared_ptr<Geometry> data)
		{
			return d3d::create_ib(m_device.get(), data);
		}
		ShaderResourceView* load_texture(FilePath path, DXGI_FORMAT desired_format)
		{
			return d3d::load_texture(m_device.get(), path, desired_format);
		}
		template<typename T>
		Buffer* create_cb()
		{
			return d3d::create_cb(m_device.get(), sizeof(T));
		}
		SamplerState* create_sampler_state(D3D11_FILTER filter)
		{
			return d3d::create_sampler_state(m_device.get(), filter);
		}
		void resize(SwapChain* swap_chain, Size desired_size, RenderTargetView** rtv)
		{
			d3d::resize(swap_chain, m_device.get(), desired_size, rtv);
		}
		void resize(Size desired_size, Texture2D** tex2d)
		{
			d3d::resize(m_device.get(), desired_size, tex2d);
		}
		Texture2D* create_texture2d(uint w, uint h, DXGI_FORMAT format, uint binding)
		{
			return d3d::create_texture2d(m_device.get(), w, h, format, binding);
		}

		RenderTargetView* create_rtv(Texture2D* tex2d)
		{
			return d3d::create_rtv(m_device.get(), tex2d);
		}
		RenderTargetView* create_rtv(SwapChain* swapchain)
		{
			return d3d::create_rtv(m_device.get(), swapchain);
		}
		DepthStencilView* create_dsv(Texture2D* tex2d)
		{
			return d3d::create_dsv(m_device.get(), tex2d);
		}
		ShaderResourceView* create_srv(Texture2D* tex2d)
		{
			return d3d::create_srv(m_device.get(), tex2d);
		}
		ShaderResourceView* create_srv(Texture2D* tex2d, DXGI_FORMAT format, D3D11_SRV_DIMENSION dimension)
		{
			return d3d::create_srv(m_device.get(), tex2d, format, dimension);
		}
		void to_dds(wstring& source_path, wstring& dest_path)
		{
			Texture2D* texture;

			D3DX11_IMAGE_LOAD_INFO ili;
			D3DX11_IMAGE_INFO src_ili;
			auto r = D3DX11GetImageInfoFromFile(source_path.c_str(), nullptr, &src_ili, nullptr );

			ZeroMemory(&ili, sizeof(D3DX11_IMAGE_LOAD_INFO ));
			ili.Width = src_ili.Width;
			ili.Height = src_ili.Height;
			ili.Usage = D3D11_USAGE_DEFAULT;
			ili.BindFlags = D3D11_BIND_SHADER_RESOURCE;
			ili.CpuAccessFlags = 0;
			ili.MipLevels = D3DX11_DEFAULT ;
			//GAMMA
			ili.Format = DXGI_FORMAT_BC1_UNORM_SRGB;
			ili.pSrcInfo = &src_ili;

			auto hr = D3DX11CreateTextureFromFile(m_device.get(), source_path.c_str(), &ili, nullptr, (ID3D11Resource**)&texture, nullptr);

			if(SUCCEEDED(hr))
			{
				Context* context;
				m_device->GetImmediateContext(&context);
				hr = D3DX11SaveTextureToFile(context, texture, D3DX11_IFF_DDS, dest_path.c_str());
				assert(SUCCEEDED(hr));
			}
		}
		unique_ptr<ID3D11Device> m_device;
	};
	struct DXContext
	{
		DXContext(unique_ptr<ID3D11DeviceContext> context);
		~DXContext();

		void set_viewport(uint width, uint height)
		{
			d3d::set_viewport(m_context.get(), width, height);
		}
		template<typename T>
		T* lock_buffer(Buffer* buffer)
		{
			return d3d::lock_buffer<T>(m_context.get(), buffer);
		}
		void unlock_buffer(Buffer* buffer)
		{
			d3d::unlock_buffer(m_context.get(), buffer);
		}
		void clear(RenderTargetView* view, float4 value)
		{
			d3d::clear(m_context.get(), view, value);
		}
		void clear(DepthStencilView* view, float value)
		{
			d3d::clear(m_context.get(), view, value);
		}
		void draw(VertexBuffer& vertex_buffer, IndexBuffer& index_buffer,
			Shader shader,
			Buffer* const* vs_cb, uint vs_cb_count, Buffer* const* ps_cb, uint ps_cb_count)
		{
			d3d::draw(m_context.get(), vertex_buffer, index_buffer, shader, vs_cb,
				vs_cb_count, ps_cb, ps_cb_count);
		}

		void draw(VertexBuffer& vertex_buffer, IndexBuffer& index_buffer,
			VS* vs, PS* ps, InputLayout* input_layout,
			Buffer* const* vs_cb, uint vs_cb_count, Buffer* const* ps_cb, uint ps_cb_count)
		{

			d3d::draw(m_context.get(), vertex_buffer, index_buffer, vs, ps, input_layout, vs_cb,
				vs_cb_count, ps_cb, ps_cb_count);
		}

		unique_ptr<ID3D11DeviceContext> m_context;
	};

	std::tuple<shared_ptr<DXDevice>, shared_ptr<DXContext>, SwapChain*> initialize(HWND window, uint w, uint h);
}

